﻿using Arch.Aspects;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel.DomainServices.Server;
using System.Reflection;
using Arch.Model;

namespace Arch.DomainServices
{
    internal class EntityDomainServiceDescriptionProvider : DomainServiceDescriptionProvider
    {
        private readonly Type _domainServiceType;

        private readonly Assembly _modelAssembly;

        public EntityDomainServiceDescriptionProvider(Type domainServiceType, DomainServiceDescriptionProvider parent, Assembly modelAssembly)
            : base(domainServiceType, parent)
        {
            _modelAssembly = modelAssembly;
            _domainServiceType = domainServiceType;
        }

        public override DomainServiceDescription GetDescription()
        {
            var description = base.GetDescription();

            var accessAttributes = new Dictionary<Type, SecurityAccess>();

            foreach (var type in _modelAssembly.GetTypes().Where(t => typeof(IEntity).IsAssignableFrom(t)))
            {
                var securityAttributes = type.GetCustomAttributes(typeof (SecurityAccess), false);
                if (securityAttributes.Count() == 0)
                {
                    accessAttributes.Add(type, new SecurityAccess());
                }
                else
                {
                    accessAttributes.Add(type, securityAttributes.First() as SecurityAccess);
                }
            }

            foreach(var overrideSecurity in GetEntityDomainFromDomainService())
            {
                accessAttributes[overrideSecurity.EntityType] = overrideSecurity;
            }

            foreach (var access in accessAttributes)
            {
                AddOperations(description, access.Key, access.Value);
            }

            return description;
        }

        private void AddOperations(DomainServiceDescription description, Type entityType, SecurityAccess security)
        {
            AddOperation(description, entityType, security.ReadAccess == Access.Authentified, DomainOperation.Query, security.ReadAccess, security.ReadRolesArray);
            AddOperation(description, entityType, security.CreateAccess == Access.Authentified, DomainOperation.Insert, security.ReadAccess, security.CreateRolesArray);
            AddOperation(description, entityType, security.UpdateAccess == Access.Authentified, DomainOperation.Update, security.ReadAccess, security.UpdateRolesArray);
            AddOperation(description, entityType, security.DeleteAccess == Access.Authentified, DomainOperation.Delete, security.ReadAccess, security.DeleteRolesArray);
        }

        private void AddOperation(
                        DomainServiceDescription description,
                        Type entityType,
                        bool requiresAuthentication,
                        DomainOperation domainOperation,
                        Access access,
                        string[] requiresRole)
        {
            if (access != Access.None &&
                EntityDomainOperationEntry.GetSpecificMethod(description.DomainServiceType, entityType, domainOperation) == null)
            {
                var attributes = new List<Attribute>();

                if (requiresAuthentication)
                {
                    attributes.Add(new RequiresAuthenticationAttribute());
                }

                if (requiresRole.Length > 0)
                {
                    attributes.Add(new RequiresRoleAttribute(requiresRole));
                }

                description.AddOperation(
                    new EntityDomainOperationEntry(description.DomainServiceType, entityType, domainOperation, new AttributeCollection(attributes.ToArray())));
            }
        }

        private IEnumerable<DomainSecurityAccess> GetEntityDomainFromDomainService()
        {
            return (DomainSecurityAccess[])_domainServiceType.GetCustomAttributes(typeof(DomainSecurityAccess), true);
        }
    }
}
